﻿using System;
using System.Diagnostics;
using RayDen.Library.Core;
using RayDen.Library.Core.Components;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Entity.Frames;

namespace RayDen.RayEngine.Data
{
    public class SkyLight : BaseInfiniteLight
    {



        protected Vector sundir;
        private RgbSpectrum gain;
        protected float turbidity;
        protected float thetaS;
        protected float phiS;
        protected double zenith_Y, zenith_x, zenith_y;
        protected double[] perez_Y, perez_x, perez_y;

        public override string Name
        {
            get { return "SkyLight"; }
        }

        public float Turbidity
        {
            get { return this.turbidity; }
            set { this.turbidity = value; }
        }

        public Vector SunDir
        {
            get { return this.sundir; }
            set { this.sundir = value; }
        }

        public SkyLight(float turb, Vector sd)
        {
            this.turbidity = turb;
            this.sundir = sd.Normalize();
            this.gain = new RgbSpectrum(1f);
            this.Init();
        }

        public SkyLight(FrameLightsource s)
            :this( s.Parameters.Get(SkyLightTurbulence, 2.5f),s.Parameters.Get(SkyLightSunDir, new Vector(0f,0f,1f)))
        {}

        protected void Init()
        {
            perez_Y = new double[6];
            perez_x = new double[6];
            perez_y = new double[6];
            thetaS = MathLab.SphericalTheta(ref sundir);
            phiS = MathLab.SphericalPhi(ref sundir);

            double aconst = 1.0;
            double bconst = 1.0;
            double cconst = 1.0;
            double dconst = 1.0;
            double econst = 1.0;

            double theta2 = thetaS * thetaS;
            double theta3 = theta2 * thetaS;
            double T = turbidity;
            double T2 = T * T;

            double chi = (4 / 9 - T / 120.0) * (Math.PI - 2.0 * thetaS);
            zenith_Y = (4.0453 * T - 4.9710) *Math.Tan(chi) - 0.2155 * T + 2.4192;
            zenith_Y *= 0.06;

            zenith_x =
            (0.00166 * theta3 - 0.00375 * theta2 + 0.00209 * thetaS) * T2 +
            (-0.02903 * theta3 + 0.06377 * theta2 - 0.03202 * thetaS + 0.00394) * T +
            (0.11693 * theta3 - 0.21196 * theta2 + 0.06052 * thetaS + 0.25886);

            zenith_y =
            (0.00275 * theta3 - 0.00610 * theta2 + 0.00317 * thetaS) * T2 +
            (-0.04214 * theta3 + 0.08970 * theta2 - 0.04153 * thetaS + 0.00516) * T +
            (0.15346 * theta3 - 0.26756 * theta2 + 0.06670 * thetaS + 0.26688);

            perez_Y[1] = (0.1787 * T - 1.4630) * aconst;
            perez_Y[2] = (-0.3554 * T + 0.4275) * bconst;
            perez_Y[3] = (-0.0227 * T + 5.3251) * cconst;
            perez_Y[4] = (0.1206 * T - 2.5771) * dconst;
            perez_Y[5] = (-0.0670 * T + 0.3703) * econst;

            perez_x[1] = (-0.0193 * T - 0.2592) * aconst;
            perez_x[2] = (-0.0665 * T + 0.0008) * bconst;
            perez_x[3] = (-0.0004 * T + 0.2125) * cconst;
            perez_x[4] = (-0.0641 * T - 0.8989) * dconst;
            perez_x[5] = (-0.0033 * T + 0.0452) * econst;

            perez_y[1] = (-0.0167 * T - 0.2608) * aconst;
            perez_y[2] = (-0.0950 * T + 0.0092) * bconst;
            perez_y[3] = (-0.0079 * T + 0.2102) * cconst;
            perez_y[4] = (-0.0441 * T - 1.6537) * dconst;
            perez_y[5] = (-0.0109 * T + 0.0529) * econst;

            zenith_Y /= LightUtils.PerezBase(perez_Y, 0, thetaS);
            zenith_x /= LightUtils.PerezBase(perez_x, 0, thetaS);
            zenith_y /= LightUtils.PerezBase(perez_y, 0, thetaS);
        }

        private void GetSkySpectralRadiance(float theta, float phi, out ISpectrum spect)
        {
            float theta_fin = Math.Min(theta, (MathLab.M_PI * 0.5f) - 0.001f);
            float gamma = LightUtils.RiAngleBetween(theta, phi, thetaS, phiS);

            // Compute xyY values
            float x = (float)(zenith_x * LightUtils.PerezBase(perez_x, theta_fin, gamma));
            float y = (float)(zenith_y * LightUtils.PerezBase(perez_y, theta_fin, gamma));
            float Y = (float)(zenith_Y * LightUtils.PerezBase(perez_Y, theta_fin, gamma));

            if (!GlobalConfiguration.Instance.SpectralRendering)
            {
                RgbSpectrum rgbl;
                LightUtils.ChromaticityToSpectrum(Y, x, y, out rgbl);

                rgbl = rgbl.Abs();
                if (rgbl.IsBlack() || rgbl.IsNegative())
                {
                    rgbl = new RgbSpectrum(1f, 0f ,0f);
                }

                spect = rgbl.Abs();
            }
            else
            {
                SampledSpectrum s;
                LightUtils.ChromaticityToSpectrum(Y, x, y, out s);
                spect = s;
            }
        }

        public override ISpectrum Le(ref Vector dr)
        {
            var dir = dr;
            float theta = MathLab.SphericalTheta(ref dir);
            float phi = MathLab.SphericalPhi(ref dir);

            ISpectrum s;
            GetSkySpectralRadiance(theta, phi, out s);

            return s;
        }


    }
}